/*
 * Copyright 2016 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SKSL_LAYOUT
#define SKSL_LAYOUT

#include "SkString.h"
#include "SkSLUtil.h"

namespace SkSL {

/**
 * Represents a layout block appearing before a variable declaration, as in:
 *
 * layout (location = 0) int x;
 */
struct Layout {
    enum Primitive {
        kUnspecified_Primitive = -1,
        kPoints_Primitive,
        kLines_Primitive,
        kLineStrip_Primitive,
        kLinesAdjacency_Primitive,
        kTriangles_Primitive,
        kTriangleStrip_Primitive,
        kTrianglesAdjacency_Primitive
    };

    // These are used by images in GLSL. We only support a subset of what GL supports.
    enum class Format {
        kUnspecified = -1,
        kRGBA32F,
        kR32F,
        kRGBA16F,
        kR16F,
        kRGBA8,
        kR8,
        kRGBA8I,
        kR8I,
    };

    static const char* FormatToStr(Format format) {
        switch (format) {
            case Format::kUnspecified:  return "";
            case Format::kRGBA32F:      return "rgba32f";
            case Format::kR32F:         return "r32f";
            case Format::kRGBA16F:      return "rgba16f";
            case Format::kR16F:         return "r16f";
            case Format::kRGBA8:        return "rgba8";
            case Format::kR8:           return "r8";
            case Format::kRGBA8I:       return "rgba8i";
            case Format::kR8I:          return "r8i";
        }
        SkFAIL("Unexpected format");
        return "";
    }

    static bool ReadFormat(SkString str, Format* format) {
        if (str == "rgba32f") {
            *format = Format::kRGBA32F;
            return true;
        } else if (str == "r32f") {
            *format = Format::kR32F;
            return true;
        } else if (str == "rgba16f") {
            *format = Format::kRGBA16F;
            return true;
        } else if (str == "r16f") {
            *format = Format::kR16F;
            return true;
        } else if (str == "rgba8") {
            *format = Format::kRGBA8;
            return true;
        } else if (str == "r8") {
            *format = Format::kR8;
            return true;
        } else if (str == "rgba8i") {
            *format = Format::kRGBA8I;
            return true;
        } else if (str == "r8i") {
            *format = Format::kR8I;
            return true;
        }
        return false;
    }

    Layout(int location, int offset, int binding, int index, int set, int builtin,
           int inputAttachmentIndex, bool originUpperLeft, bool overrideCoverage,
           bool blendSupportAllEquations, Format format, bool pushconstant, Primitive primitive,
           int maxVertices, int invocations)
    : fLocation(location)
    , fOffset(offset)
    , fBinding(binding)
    , fIndex(index)
    , fSet(set)
    , fBuiltin(builtin)
    , fInputAttachmentIndex(inputAttachmentIndex)
    , fOriginUpperLeft(originUpperLeft)
    , fOverrideCoverage(overrideCoverage)
    , fBlendSupportAllEquations(blendSupportAllEquations)
    , fFormat(format)
    , fPushConstant(pushconstant)
    , fPrimitive(primitive)
    , fMaxVertices(maxVertices)
    , fInvocations(invocations) {}

    Layout()
    : fLocation(-1)
    , fOffset(-1)
    , fBinding(-1)
    , fIndex(-1)
    , fSet(-1)
    , fBuiltin(-1)
    , fInputAttachmentIndex(-1)
    , fOriginUpperLeft(false)
    , fOverrideCoverage(false)
    , fBlendSupportAllEquations(false)
    , fFormat(Format::kUnspecified)
    , fPushConstant(false)
    , fPrimitive(kUnspecified_Primitive)
    , fMaxVertices(-1)
    , fInvocations(-1) {}

    SkString description() const {
        SkString result;
        SkString separator;
        if (fLocation >= 0) {
            result += separator + "location = " + to_string(fLocation);
            separator = ", ";
        }
        if (fOffset >= 0) {
            result += separator + "offset = " + to_string(fOffset);
            separator = ", ";
        }
        if (fBinding >= 0) {
            result += separator + "binding = " + to_string(fBinding);
            separator = ", ";
        }
        if (fIndex >= 0) {
            result += separator + "index = " + to_string(fIndex);
            separator = ", ";
        }
        if (fSet >= 0) {
            result += separator + "set = " + to_string(fSet);
            separator = ", ";
        }
        if (fBuiltin >= 0) {
            result += separator + "builtin = " + to_string(fBuiltin);
            separator = ", ";
        }
        if (fInputAttachmentIndex >= 0) {
            result += separator + "input_attachment_index = " + to_string(fBuiltin);
            separator = ", ";
        }
        if (fOriginUpperLeft) {
            result += separator + "origin_upper_left";
            separator = ", ";
        }
        if (fOverrideCoverage) {
            result += separator + "override_coverage";
            separator = ", ";
        }
        if (fBlendSupportAllEquations) {
            result += separator + "blend_support_all_equations";
            separator = ", ";
        }
        if (Format::kUnspecified != fFormat) {
            result += separator + FormatToStr(fFormat);
            separator = ", ";
        }
        if (fPushConstant) {
            result += separator + "push_constant";
            separator = ", ";
        }
        switch (fPrimitive) {
            case kPoints_Primitive:
                result += separator + "points";
                separator = ", ";
                break;
            case kLines_Primitive:
                result += separator + "lines";
                separator = ", ";
                break;
            case kLineStrip_Primitive:
                result += separator + "line_strip";
                separator = ", ";
                break;
            case kLinesAdjacency_Primitive:
                result += separator + "lines_adjacency";
                separator = ", ";
                break;
            case kTriangles_Primitive:
                result += separator + "triangles";
                separator = ", ";
                break;
            case kTriangleStrip_Primitive:
                result += separator + "triangle_strip";
                separator = ", ";
                break;
            case kTrianglesAdjacency_Primitive:
                result += separator + "triangles_adjacency";
                separator = ", ";
                break;
            case kUnspecified_Primitive:
                break;
        }
        if (fMaxVertices >= 0) {
            result += separator + "max_vertices = " + to_string(fMaxVertices);
            separator = ", ";
        }
        if (fInvocations >= 0) {
            result += separator + "invocations = " + to_string(fInvocations);
            separator = ", ";
        }
        if (result.size() > 0) {
            result = "layout (" + result + ")";
        }
        return result;
    }

    bool operator==(const Layout& other) const {
        return fLocation                 == other.fLocation &&
               fOffset                   == other.fOffset &&
               fBinding                  == other.fBinding &&
               fIndex                    == other.fIndex &&
               fSet                      == other.fSet &&
               fBuiltin                  == other.fBuiltin &&
               fInputAttachmentIndex     == other.fInputAttachmentIndex &&
               fOriginUpperLeft          == other.fOriginUpperLeft &&
               fOverrideCoverage         == other.fOverrideCoverage &&
               fBlendSupportAllEquations == other.fBlendSupportAllEquations &&
               fFormat                   == other.fFormat &&
               fPrimitive                == other.fPrimitive &&
               fMaxVertices              == other.fMaxVertices &&
               fInvocations              == other.fInvocations;
    }

    bool operator!=(const Layout& other) const {
        return !(*this == other);
    }

    int fLocation;
    int fOffset;
    int fBinding;
    int fIndex;
    int fSet;
    // builtin comes from SPIR-V and identifies which particular builtin value this object
    // represents.
    int fBuiltin;
    // input_attachment_index comes from Vulkan/SPIR-V to connect a shader variable to the a
    // corresponding attachment on the subpass in which the shader is being used.
    int fInputAttachmentIndex;
    bool fOriginUpperLeft;
    bool fOverrideCoverage;
    bool fBlendSupportAllEquations;
    Format fFormat;
    bool fPushConstant;
    Primitive fPrimitive;
    int fMaxVertices;
    int fInvocations;
};

} // namespace

#endif
